/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.streamline.services.json;

import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.json.converters.DateConverter;
import com.vii.streamline.services.json.converters.DoubleConverter;
import com.vii.streamline.services.json.converters.IntegerConverter;
import com.vii.streamline.services.json.converters.LongConverter;
import com.vii.streamline.services.pool.SimpleObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.svenson.JSON;
import org.svenson.JSONParser;
import org.svenson.converter.DefaultTypeConverterRepository;
import org.svenson.converter.TypeConverter;
import org.svenson.converter.TypeConverterRepository;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Basic services class helping JSON handling.
 */
public class JsonServices {

    private static SimpleObjectPool<JSON> jsonPool;
    private static SimpleObjectPool<JSONParser> parserPool;
    private static DefaultTypeConverterRepository typeConverterRepository;


    public static final String LIST_PREFIX = "[";
    public static final String LIST_POSTFIX = "]";
    public static final String OBJECT_PREFIX = "{";
    public static final String OBJECT_POST = "}";

    private static String CDATA_PREFIX = "<![CDATA[";
    private static String CDATA_POSTFIX = "]]>";

    private static Logger logger;

    static {
        typeConverterRepository = new DefaultTypeConverterRepository();
        typeConverterRepository.addTypeConverter(new DateConverter());
        typeConverterRepository.addTypeConverter(new IntegerConverter());
        typeConverterRepository.addTypeConverter(new LongConverter());
        typeConverterRepository.addTypeConverter(new DoubleConverter());

        jsonPool = new SimpleObjectPool<JSON>(JSON.class);
        parserPool = new SimpleObjectPool<JSONParser>(JSONParser.class) {
            @Override
            public void initialize(JSONParser jsonParser) {
                jsonParser.setTypeHints(new HashMap<String, Class>());
            }
        };

        logger = LoggerFactory.getLogger(JsonServices.class);
    }

    public static TypeConverterRepository getTypeConverterRepository() {
        return typeConverterRepository;
    }

    public static <T extends TypeConverter> T getTypeConverter(Class<T> ct) {
        return typeConverterRepository.<T>getConverterByType(ct);
    }

    public static <T extends TypeConverter> T getTypeConverter(String id) {
        return (T) typeConverterRepository.getConverterById(id);
    }

    public static void addTypeConverter(TypeConverter converter) {
        typeConverterRepository.addTypeConverter(converter);
    }

    public static void addTypeConverter(String name, TypeConverter converter) {
        typeConverterRepository.addTypeConverter(name, converter);
    }

    public static JSONParser getParser() {
        JSONParser parser = parserPool.getInstance();
        parser.setTypeConverterRepository(typeConverterRepository);
        return parser;
    }

    public static void retrieveParser(JSONParser parser) {
        parserPool.returnObject(parser);
    }

    public static JSON getJson() {
        JSON json = jsonPool.getInstance();
        json.setTypeConverterRepository(typeConverterRepository);
        return json;
    }

    public static void retrieveJson(JSON json) {
        jsonPool.returnObject(json);
    }

    /**
     * Serialize an object into JSON string ignoring the given properties
     */
    public static String toJSON(Object o, List<String> ignoredProps) {
        JSON serializer = getJson();
        try {
            return serializer.forValue(o, ignoredProps);
        } finally {
            retrieveJson(serializer);
        }
    }

    /**
     * Serialize an object into JSON string packet into XML entity
     */
    public static String toJsonXML(Object o) throws StreamLineException {
        return CDATA_PREFIX + toJSON(o) + CDATA_POSTFIX;
    }

    /**
     * Serialize an object into JSON string
     */
    public static String toJSON(Object o) throws StreamLineException {
        JSON serializer = getJson();
        try {
            return serializer.forValue(o);
        } catch (Exception e) {
            throw new StreamLineException(e.getMessage(), e);
        } finally {
            retrieveJson(serializer);
        }
    }

    /**
     * Serialize an object into JSON string
     */
    public static String[] toJSON(Object... os) throws StreamLineException {
        String[] jsons = new String[os.length];
        for (int i = 0; i < os.length; ++i)
            jsons[i] = toJSON(os[i]);
        return jsons;
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> T parseJSON(String jsonText, Class<T> type, Map<String, Class> typeHints) throws StreamLineException {
        if (typeHints == null)
            throw new StreamLineException("Invalid type hint parameters!");

        JSONParser parser = getParser();
        parser.setTypeHints(new HashMap<String, Class>());
        try {
            for (String typeHint : typeHints.keySet())
                parser.addTypeHint(typeHint, typeHints.get(typeHint));

            logger.debug("To parse: " + jsonText);
            logger.debug("To type: " + type);
            logger.debug("TypeHints: " + typeHints);

            return parser.parse(type, jsonText);
        } finally {
            retrieveParser(parser);
        }
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> T parseJSON(String jsonText, Map<String, Class> typeHints) throws StreamLineException {
        if (typeHints == null)
            throw new StreamLineException("Invalid type hint parameters!");

        JSONParser parser = getParser();
        parser.setTypeHints(new HashMap<String, Class>());
        try {
            for (String typeHint : typeHints.keySet())
                parser.addTypeHint(typeHint, typeHints.get(typeHint));

            logger.debug("To parse: " + jsonText);
            logger.debug("TypeHints: " + typeHints);

            return (T)parser.parse( jsonText );
        } finally {
            retrieveParser(parser);
        }
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> T parseJSON(String jsonText, String typeHint, Class hintClass) throws StreamLineException {
        JSONParser parser = getParser();
        try {
            parser.addTypeHint(typeHint, hintClass);

            logger.debug("To parse: " + jsonText);
            logger.debug("A hint: " + typeHint + " " + hintClass);

            return (T) parser.parse(jsonText);
        } finally {
            retrieveParser(parser);
        }
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> T parseJSON(String jsonText, Class<T> type, String typeHint, Class hintClass) throws StreamLineException {
        JSONParser parser = getParser();
        try {
            parser.addTypeHint(typeHint, hintClass);


            logger.debug("To parse: " + jsonText);
            logger.debug("A hint: " + typeHint + " " + hintClass);

            return parser.parse(type, jsonText);
        } finally {
            retrieveParser(parser);
        }
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> T parseJSON(String jsonText, Class<T> type) throws StreamLineException {
        JSONParser parser = getParser();
        try {
            logger.debug("To parse: " + jsonText);
            logger.debug("To type: " + type);

            return parser.parse(type, jsonText);
        } finally {
            retrieveParser(parser);
        }
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static List parseJSON(Iterable<String> jsonText) {
        LinkedList l = new LinkedList();
        for (String s : jsonText) {
            l.add(parseJSON(s));
        }
        return l;
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> List<T> parseJSON(Iterable<String> jsonText, Class<T> type) throws StreamLineException {
        LinkedList<T> l = new LinkedList<T>();
        for (String s : jsonText) {
            l.add(parseJSON(s, type));
        }
        return l;
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> List<T> parseJSON(Iterable<String> jsonText, Class<T> type, String typeHint, Class hintClass) throws StreamLineException {
        LinkedList<T> l = new LinkedList<T>();
        for (String s : jsonText) {
            l.add(parseJSON(s, type, typeHint, hintClass));
        }
        return l;
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> List<T> parseJSON(Iterable<String> jsonText, Class<T> type, Map<String, Class> typeHints) throws StreamLineException {
        LinkedList<T> l = new LinkedList<T>();
        for (String s : jsonText) {
            l.add(parseJSON(s, type, typeHints));
        }
        return l;
    }

    /**
     * Deserialize a JSON string into object of a given type
     */
    public static <T> T parseJSON(String jsonText) {
        JSONParser parser = getParser();
        try {
            return (T) parser.parse(jsonText);
        } finally {
            retrieveParser(parser);
        }
    }

    public static void main(String[] args) throws StreamLineException {
        String name = "Imi";

        System.out.println( "" + JsonServices.parseJSON( JsonServices.toJSON( name), String.class ) );
    }

}
